Repository navigation
Fix #1027: clear stuck "Needs input" when the follow-up event turnId drifts - #5666
RubiconPerform wants to merge 4 commits into
Conversation
…vent's turn id drifts A Claude `Notification` sets the session lifecycle to `needsInput` but does not advance the active turn (it stays pointing at the prior prompt's turn). The follow-up events that should clear it -- `stop` (-> idle), `prompt-submit` (-> running) and `pre-tool-use` (-> running) -- are all gated by `ClaudeHookSessionStore.isCurrent(...)`, which requires the event's turnId (and sessionId) to match the active turn. On a resume / turn drift the follow-up event carries a different turnId, so `isCurrent` returns false, the clearing mutation is dropped, and the sidebar is stranded on "Needs input". It is intermittent because it depends on whether the turn ids line up. Add `ClaudeHookSessionStore.isCurrentOrClearsStaleNeedsInput(...)` and use it ONLY in the three Claude clearing transitions. It returns true when `isCurrent` is true, or when the event belongs to the SAME active session and that session is currently stuck on `.needsInput` (ignoring the turnId). The `active.sessionId == sessionId` requirement keeps a DIFFERENT-session event failing closed, so the protection in `testClaudeStopFromPreviousSessionDoesNotClobberClearRunningStatus` is preserved. The needsInput-SETTING paths (`Notification`, `AskUserQuestion`) keep the strict gate, so this relaxation can only ever clear a stale "Needs input", never spuriously raise one. Adds three regression tests that drive the real CLI hooks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@RubiconPerform is attempting to deploy a commit to the Manaflow Team on Vercel. A member of the Team first needs to authorize it. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughRelax CLEARING eligibility so same-session events with a drifted turnId can clear a stale ChangesClaude needsInput lifecycle clearing
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 error, 1 warning, 1 inconclusive)
✅ Passed checks (16 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@codex review |
@RubiconPerform I can't start this review because your workspace has reached its free monthly review limit. Reviews resume at the start of your next billing cycle. Paid plans include much higher monthly review limits. Upgrade now to resume reviews. |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Fetch the PR diff
gh pr view 5666 --repo manaflow-ai/cmux --json files,additions,deletions,changedFiles
echo "---DIFF---"
gh pr diff 5666 --repo manaflow-ai/cmuxLength of output: 17418
🟡 Missing regression test for
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ed5db2578e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Greptile SummaryFixes the stuck "Needs input" sidebar badge (issue #1027) by introducing
Confidence Score: 5/5The change is safe to merge; the gate logic is atomic, the cross-session guardrail is preserved, and all three clearing transitions now have regression coverage including the previously missing prompt-submit and AskUserQuestion-drift cases. The atomicity issue (double withLockedState) and the AskUserQuestion fall-through regression identified in earlier rounds are both fully addressed. The new gate is self-contained and the test suite explicitly covers the same-session/drifted, cross-session, and AskUserQuestion-drift scenarios. The only remaining gap — partial locale coverage in the string catalog — is consistent with the established pattern for the entire agent.* namespace and does not affect functionality. No files require special attention for merging; the Localizable.xcstrings locale gap is a follow-up quality item. Important Files Changed
Sequence DiagramsequenceDiagram
participant C as Claude CLI
participant G as Gate (shouldApplyClaudeHookClearingMutation)
participant S as ClaudeHookSessionStore
participant UI as Sidebar Badge
Note over C,UI: Happy path — same session, same turnId
C->>G: "stop(sessionId=S1, turnId=T1)"
G->>S: isCurrentOrClearsStaleNeedsInput (single withLockedState)
S-->>G: "active.sessionId==S1, activeTurnId==T1 → true"
G-->>C: allow
C->>UI: set_agent_lifecycle idle
Note over C,UI: Bug 1027 — same session, drifted turnId, needsInput
C->>G: "stop(sessionId=S1, turnId=T2)"
G->>S: isCurrentOrClearsStaleNeedsInput (single withLockedState)
S-->>G: "active.sessionId==S1, activeTurnId≠T2, lifecycle==needsInput → true"
G-->>C: allow (relaxed gate)
C->>UI: set_agent_lifecycle idle — badge cleared
Note over C,UI: Guardrail — different session
C->>G: "stop(sessionId=S2, turnId=T9)"
G->>S: isCurrentOrClearsStaleNeedsInput (single withLockedState)
S-->>G: "active.sessionId==S1 ≠ S2 → false"
G-->>C: block
C->>UI: no mutation — S1 stays on needsInput
Reviews (6): Last reviewed commit: "Address review: localize the Claude "Wai..." | Re-trigger Greptile |
| } | ||
| } | ||
|
|
||
| /// Gate for Claude CLEARING transitions only. Identical to | ||
| /// `shouldApplyClaudeHookVisibleMutation` except it also applies when the SAME active session | ||
| /// is stuck on `.needsInput` after the follow-up event's `turnId` drifted, so a stale | ||
| /// "Needs input" badge is cleared instead of being stranded. A different-session event still | ||
| /// fails closed via `isCurrentOrClearsStaleNeedsInput`. See | ||
| /// https://github.com/manaflow-ai/cmux/issues/1027. | ||
| private func shouldApplyClaudeHookClearingMutation( | ||
| sessionStore: ClaudeHookSessionStore, | ||
| parsedInput: ClaudeHookParsedInput, | ||
| workspaceId: String, | ||
| telemetry: CLISocketSentryTelemetry | ||
| ) -> Bool { | ||
| do { | ||
| return try sessionStore.isCurrentOrClearsStaleNeedsInput( | ||
| sessionId: parsedInput.sessionId, | ||
| workspaceId: workspaceId, | ||
| turnId: parsedInput.turnId | ||
| ) | ||
| } catch { | ||
| telemetry.breadcrumb( | ||
| "claude-hook.is-current.error", | ||
| data: [ | ||
| "error": String(describing: error), | ||
| "session_id": parsedInput.sessionId ?? "", | ||
| "workspace_id": workspaceId, | ||
| "turn_id": parsedInput.turnId ?? "", | ||
| ] | ||
| ) | ||
| return true | ||
| } | ||
| } | ||
|
|
||
| private func shouldReplaceStoppedClaudeSession( | ||
| sessionStore: ClaudeHookSessionStore, | ||
| parsedInput: ClaudeHookParsedInput, |
There was a problem hiding this comment.
Symptom patch that leaves the bad state representable
The root cause is that the Notification handler sets needsInput but does not advance active.turnId, so every subsequent event arrives with a drifted turn ID and the strict gate drops it. This PR introduces a new side-channel gate (isCurrentOrClearsStaleNeedsInput) and a parallel wrapper (shouldApplyClaudeHookClearingMutation) that work around the dropped mutations after the fact. A minimal alternative that names the invariant: in the Notification handler, carry forward the event's turnId so active.turnId advances to match, keeping isCurrent as the single gate for all transitions.
Rule Used: Flag Swift fixes that patch symptoms while leaving... (source)
Greptile SummaryAdds a relaxed clearing gate (
Confidence Score: 3/5The different-session guard is correct, but the new The new
Important Files Changed
Reviews (1): Last reviewed commit: "Fix #1027: clear a stuck "Needs input" w..." | Re-trigger Greptile |
Greptile SummaryThis PR fixes the "Needs input" badge getting permanently stuck in the sidebar by introducing a relaxed clearing gate (
Confidence Score: 3/5The relaxed clearing gate introduces a regression in the pre-tool-use handler: when Claude follows a Notification with an AskUserQuestion in a new turn, the stale-needsInput fallback fires the clearing path and the badge incorrectly transitions to Running instead of Needs input, a state the sidebar cannot recover from without another user interaction. The stop and prompt-submit paths are straightforward and the relaxation is safe there. The pre-tool-use path is more complex because it has two diverging outcomes depending on the tool name. The clearing gate is applied before the tool-name check, so a drifted-turnId AskUserQuestion event passes the clearing guard, the strict AskUserQuestion branch is then skipped, and lifecycle lands on .running. Because the following Notification is also gated out by the strict check, the badge is stranded at Running — worse than the pre-fix stuck-needsInput behavior. CLI/cmux.swift — specifically the pre-tool-use handler around the AskUserQuestion branch (lines 22110–22169) where the new clearing gate and the strict AskUserQuestion gate interact. Important Files Changed
Sequence DiagramsequenceDiagram
participant U as User
participant CH as Claude Hook CLI
participant SS as ClaudeHookSessionStore
participant UI as Sidebar Badge
Note over CH,UI: Bug scenario (pre-fix): turnId drift strands Needs input
U->>CH: prompt-submit (turn-1)
CH->>SS: upsert(turn-1, running)
CH->>UI: running
CH->>SS: notification → needsInput (turn stays turn-1)
CH->>UI: Needs input
CH->>SS: stop(turn-2, same session)
SS-->>CH: "isCurrent=false (turn-2 ≠ turn-1)"
Note over CH: gated out — badge stranded
Note over CH,UI: Fix: isCurrentOrClearsStaleNeedsInput relaxes clearing gate
CH->>SS: stop(turn-2, same session)
SS-->>CH: "isCurrent=false, but needsInput+same session → true"
CH->>SS: upsert(idle)
CH->>UI: idle
Note over CH,UI: Regression: pre-tool-use AskUserQuestion + drifted turnId
CH->>SS: pre-tool-use(AskUserQuestion, turn-2)
SS-->>CH: clearing gate passes (needsInput fallback)
Note over CH: strict AskUserQuestion check fails (turn-2 ≠ turn-1)
CH->>SS: upsert(running) falls through
CH->>UI: Running (wrong)
CH->>SS: notification(turn-2) — strict gate fails, gated out
Note over UI: Badge stranded at Running
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CLI/cmux.swift`:
- Around line 1227-1246: The helper isCurrentOrClearsStaleNeedsInput currently
calls isCurrent(...) outside the withLockedState lock then reopens the store to
re-check active session state, allowing a race; instead, acquire the same
withLockedState once and evaluate both predicates atomically: perform the
normalizedSessionId/normalizedWorkspace checks and call the in-lock equivalent
of isCurrent (i.e., check whether the provided sessionId/workspaceId/turnId
match the current active session/turn) and also check
state.sessions[normalizedSessionId]?.agentLifecycle == .needsInput inside that
same locked snapshot; update the function so all reads (normalizeOptional usage,
activeSessionsByWorkspace lookup, session lifecycle check, and any current-turn
comparison) happen within a single withLockedState closure to prevent races.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: c05fd7c9-e2ac-4824-baa7-e4907957ee51
📒 Files selected for processing (2)
CLI/cmux.swiftcmuxTests/CLINotifyProcessIntegrationRegressionTests.swift
Codex review flagged that with the relaxed pre-tool-use clearing gate, a PreToolUse for AskUserQuestion whose turnId had drifted (while the same active session was already on needsInput) passed the relaxed entry guard, failed the inner strict gate, and then fell through to the generic path that upserts .running -- so an actual user-question event wiped the live "Needs input". Restructure the pre-tool-use handler so AskUserQuestion is handled as its own needsInput-SETTING event BEFORE the relaxed clearing gate and always returns. Raising needsInput keeps the strict gate; a stale/drifted AskUserQuestion now leaves state as-is instead of clearing it. The relaxed clearing gate now only governs non-AskUserQuestion pre-tool-use (Claude resuming work -> running). Also addressing CodeRabbit: - Add the missing prompt-submit turn-drift regression test (the third clearing transition, alongside the stop and pre-tool-use tests). - Add a regression test asserting a drifted AskUserQuestion does NOT clear needsInput (locks in the codex fix). - Document the intentional two-phase read in isCurrentOrClearsStaleNeedsInput. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Pushed @codex (P2 — a drifted
Both changed files are @codex review |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
gh pr diff 5666 --repo manaflow-ai/cmuxLength of output: 26903
The five new tests collectively cover all three clearing transitions under turn drift, the cross-session guardrail, and the 🐇 ✨ [resolve] |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 076c85f9af
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // is cleared even when this event's turnId drifted from the active turn | ||
| // (https://github.com/manaflow-ai/cmux/issues/1027). A different-session event still | ||
| // fails closed. | ||
| guard shouldApplyClaudeHookClearingMutation( |
There was a problem hiding this comment.
Update active turn after drifted pre-tool-use
When this relaxed gate admits a same-session pre-tool-use whose turnId drifted, the handler clears the lifecycle to running but never advances activeSessionsByWorkspace to parsedInput.turnId (unlike prompt-submit, which upserts with markActive: true and the new turn). In the repaired flow prompt-submit(turn-1) -> Notification(needsInput) -> pre-tool-use(turn-2), the active turn remains turn-1, so a later strict-gated Notification or AskUserQuestion for turn-2 is treated as stale and the next permission/question prompt will not surface.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
CLI/cmux.swift (1)
1239-1245:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMake the relaxed clearing gate atomic.
Line 1239 still leaves a race between
isCurrent(...)and the secondwithLockedStateread. If a newer same-sessionneedsInputupdate lands in that gap, an older CLEARING event can still pass here and clear a live badge, which reintroduces the false-negative this PR is meant to remove. Evaluate the current-turn check and the stale-clear fallback from one locked snapshot.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@CLI/cmux.swift` around lines 1239 - 1245, The relaxed gate currently has a TOCTOU between isCurrent(...) and the later withLockedState closure, allowing an older CLEARING event to clear a live needsInput badge; fix this by performing the current-turn check and the stale-clear fallback inside a single withLockedState snapshot so both activeSessionsByWorkspace[normalizedWorkspace].sessionId == normalizedSessionId and the needsInput/CLEARING logic are evaluated atomically; specifically, move the isCurrent evaluation into the same withLockedState closure (or fetch the same-session marker from state once and use it for both the current-turn and stale-clear decisions) so decisions around activeSessionsByWorkspace, normalizedSessionId and needsInput are based on one locked snapshot.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CLI/cmux.swift`:
- Around line 22131-22145: Replace the hard-coded "Waiting" subtitle passed to
sessionStore.upsert with a localized string key; specifically, change the value
assigned to lastSubtitle in the call to sessionStore.upsert (the code block
using sessionStore.upsert and the lastSubtitle parameter) to use
String(localized: "CLAUDE_AGENT_WAITING_SUBTITLE", defaultValue: "Waiting") (or
your project's localization helper) and add the key
"CLAUDE_AGENT_WAITING_SUBTITLE" with translations to
Resources/Localizable.xcstrings for all supported locales so the UI displays a
localized subtitle instead of the hard-coded English text.
---
Duplicate comments:
In `@CLI/cmux.swift`:
- Around line 1239-1245: The relaxed gate currently has a TOCTOU between
isCurrent(...) and the later withLockedState closure, allowing an older CLEARING
event to clear a live needsInput badge; fix this by performing the current-turn
check and the stale-clear fallback inside a single withLockedState snapshot so
both activeSessionsByWorkspace[normalizedWorkspace].sessionId ==
normalizedSessionId and the needsInput/CLEARING logic are evaluated atomically;
specifically, move the isCurrent evaluation into the same withLockedState
closure (or fetch the same-session marker from state once and use it for both
the current-turn and stale-clear decisions) so decisions around
activeSessionsByWorkspace, normalizedSessionId and needsInput are based on one
locked snapshot.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 00871f95-8604-441f-919b-a703af88e6d6
📒 Files selected for processing (2)
CLI/cmux.swiftcmuxTests/CLINotifyProcessIntegrationRegressionTests.swift
Greptile (P1) + CodeRabbit (Major): isCurrentOrClearsStaleNeedsInput called isCurrent() (one withLockedState) and then re-acquired the lock for the needsInput check -- a non-atomic two-phase read where a concurrent session update between the two lock windows could flip the decision. Inline the isCurrent predicate so the turn-identity check and the needsInput check are evaluated from a SINGLE withLockedState snapshot. This also removes the post-isCurrent re-normalization guard that Greptile flagged as unreachable dead code. Greptile (P2): give shouldApplyClaudeHookClearingMutation its own "claude-hook.clearing-gate.error" telemetry breadcrumb so its errors are distinguishable from the strict gate's "claude-hook.is-current.error". Behavior is unchanged (verified by the standalone gate harness across all clear/block/no-over-clear/same-turn cases); this is a structural hardening of the same logic. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Pushed Atomic gate (@greptile P1, @coderabbitai Major): Distinct breadcrumb (@greptile P2): the clearing gate now emits Already in "Advance the active turn" / "symptom patch" (@codex P2, @greptile P2): agreed these describe the more complete model. I've kept this PR scoped to the stuck-badge root cause deliberately: (a) the turn-non-advancement is pre-existing, not introduced here; (b) advancing
Verification: built and ran the @codex review |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
gh pr diff 5666 --repo manaflow-ai/cmuxLength of output: 27189
The 🐇 ✨ [resolve] |
|
All concerns from the previous round are addressed in P1 items — fully resolved:
P2 items — resolved:
Remaining P2 "symptom patch" concern: The reasoning is sound — The implementation is clean, atomic, and well-tested. LGTM. ✅ Tip: You can customize Greptile's behavior for this repo with |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 05d62c5f0c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Same session, but the turnId drifted: still clear a stale "Needs input" badge. | ||
| return state.sessions[normalizedSessionId]?.agentLifecycle == .needsInput |
There was a problem hiding this comment.
Preserve turn guard for older same-session events
When the current Claude session is waiting for input on a newer active turn, a late hook from an older turn in the same session now passes this relaxed gate solely because the session lifecycle is .needsInput. For example, with active turn-2 showing a permission prompt, a delayed Stop for turn-1 will be treated as current and the stop handler will write idle/mark the session active, clearing the real current prompt; the previous strict isCurrent check was the protection against this same-session stale-turn case. The relaxation needs some way to distinguish the intended newer drift from older stale turns before ignoring the turn mismatch.
Useful? React with 👍 / 👎.
|
@austinywang (you're assigned #1027) — status summary + one design call I'd like your steer on, plus a heads-up that the Swift CI needs a maintainer to approve the workflow run on this fork PR. Bot threads that are already addressed (re-flagged on a stale snapshot)A few open threads are re-posts of items fixed in later commits — flagging so they're not read as still-open:
The one I want your call on — @chatgpt-codex-connector's
|
|
To use Codex here, create an environment for this repo. |
Replace the hard-coded "Waiting" lastSubtitle in the pre-tool-use AskUserQuestion branch with String(localized: "agent.claude.input.subtitle.waiting", defaultValue: "Waiting"), mirroring the existing agent.codex.input.subtitle.waiting convention, and add en/ja entries to Localizable.xcstrings (en=Waiting, ja=待機中). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Localized the |
|
Tip For best results, initiate chat on the files or code changes.
🐇 🌸 If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |
Summary
What changed? Fixes #1027 — the sidebar agent status getting stuck on "Needs input".
Root cause. When Claude fires a
Notification, the CLI sets the session lifecycle toneedsInputbut does not advance the active turn (it stays pointing at the prior prompt's turn). The events that should later clear it —stop(→ idle),prompt-submit(→ running),pre-tool-use(→ running) — are all gated byClaudeHookSessionStore.isCurrent(...), which requires the event'sturnIdandsessionIdto match the workspace's active turn. On a resume / turn drift the follow-up event carries a differentturnId, soisCurrentreturnsfalse, the clearing mutation is dropped, and the badge is stranded on "Needs input". It's intermittent precisely because it depends on whether the turn ids happen to line up.Fix. New
ClaudeHookSessionStore.isCurrentOrClearsStaleNeedsInput(...), used only by the three Claude clearing handlers (stop,prompt-submit,pre-tool-use). It returnstruewhenisCurrentis true or when the event is for the same active session and that session is currently stuck on.needsInput(ignoring theturnId). Because it still requiresactive.sessionId == sessionId, a different-session event keeps failing closed — so the protection asserted bytestClaudeStopFromPreviousSessionDoesNotClobberClearRunningStatusis preserved. The needsInput-setting paths (Notification,AskUserQuestion) keep the strictisCurrentgate, so this relaxation can only ever clear a stale "Needs input", never spuriously raise one.Scoped intentionally to the stuck-
needsInputroot cause. The related "Running persists after stop" and "status pill missing on new workspaces" symptoms are separate and already have in-flight PRs (#5500 / #5662 / #5660), so I kept this minimal and did not touch the app-side aggregation or the generic-agent notification path.Two files:
CLI/cmux.swift(gate + wiring) andcmuxTests/CLINotifyProcessIntegrationRegressionTests.swift(regression tests).Testing
Three regression tests added to the already-wired
CLINotifyProcessIntegrationRegressionTests.swift, each driving the real CLI hook binary:testClaudeNotificationNeedsInputClearsOnSameSessionTurnDriftStop— prompt-submit(turn-1) → notification (→ needsInput) → stop(turn-2, same session): asserts the lifecycle clears toidle, emitsset_agent_lifecycle claude_code idle, and does not re-assert "Needs input".testClaudeNotificationNeedsInputClearsOnSameSessionTurnDriftPreToolUse— same setup,pre-tool-use(turn-2, Bash)→ clears torunning.testClaudeNotificationNeedsInputStaysStuckForDifferentSessionStop— guardrail: a different-session Stop must not clear the active session'sneedsInput.swift-tools-version: 6.0), so I could not run the fullxcodebuild -scheme cmux-unitsuite locally. What I did verify locally:swiftc -parseclean on both changed files.scripts/lint-pbxproj-test-wiring.sh→ ok (the test file is already wired; no new pbxproj entries needed).isCurrent+isCurrentOrClearsStaleNeedsInputfunctions and asserting: same-session turn-drift clears, different-session stays blocked, non-needsInputstatus is not over-cleared, and same-turn behavior is unchanged — all pass.Please run the suite on CI to confirm end-to-end:
Demo Video
N/A — internal status-state logic, covered by the new integration tests.
Checklist
swiftc -parse+ pbxproj wiring lint; fullxcodebuildsuite needs Xcode 26 — see note above)Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Summary by cubic
Fixes #1027 by clearing a stuck "Needs input" even when the follow-up
stop,prompt-submit, orpre-tool-useevent has a differentturnId. Adds an atomic, same-session clearing gate that preserves cross-session safety, and localizes the Claude "Waiting" subtitle.ClaudeHookSessionStore.isCurrentOrClearsStaleNeedsInputandshouldApplyClaudeHookClearingMutation; applied tostop,prompt-submit, and non-AskUserQuestionpre-tool-use. The gate runs under a singlewithLockedStateand logs withclaude-hook.clearing-gate.error.pre-tool-usesoAskUserQuestionis handled first as a needsInput-setting event with the strict gate and an early return; a drifted/stale question can’t clear a live "Needs input". Localized the "Waiting" subtitle viaagent.claude.input.subtitle.waiting(en/ja).AskUserQuestiondo not clear the state.Written for commit 1733504. Summary will update on new commits.
Summary by CodeRabbit
Bug Fixes
Tests
New Features